/*    

    bobbietalk - a visual programming environment for toy robotics

    Copyright (C) 2013  Roberto Tiella

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

*/
package bobbietalk.gui.dialogs;

import bobbietalk.Utilities;
import bobbietalk.gui.MainForm;
import bobbietalk.simulator.AbstractSimpleRobot;
import bobbietalk.simulator.NxtRobot;
import bobbietalk.simulator.SimpleMotorsPlatform;
import bobbietalk.simulator.controllers.BshController;
import java.util.Date;
import java.util.Observable;
import java.util.Observer;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.JScrollBar;

/**
 *
 * @author roberto
 */
public class LocalSimulationWindow extends javax.swing.JDialog {

    private UserMessagesDialog userDialog;
    
    private String code;
    private AbstractSimpleRobot robot;
    private Thread simulationThread;
    private Thread controllerThread;
    private ArrayBlockingQueue<Integer> hold = new ArrayBlockingQueue(1);

    /**
     * Creates new form LocalSimulatorWindow
     */
    public LocalSimulationWindow(java.awt.Frame parent, boolean modal) {
        super(parent, modal);
        
        userDialog = new UserMessagesDialog(parent, true);
        
        initComponents();
        final JScrollBar hsb = jScrollPane1.getHorizontalScrollBar();

        int h = hsb.getMinimum() + (hsb.getMaximum() - hsb.getMinimum()) / 2;
        hsb.setValue(h);

        final JScrollBar vsb = jScrollPane1.getVerticalScrollBar();
        int v = vsb.getMinimum() + (vsb.getMaximum() - vsb.getMinimum()) / 2;
        vsb.setValue(v);
        
        pauseButton.setVisible(false);
        resumeButton.setVisible(false);

        suspend();
        initSimulation();

    }

    /**
     * This method is called from within the constructor to initialize the form.
     * WARNING: Do NOT modify this code. The content of this method is always
     * regenerated by the Form Editor.
     */
    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        worldCanvas = new bobbietalk.simulator.gui.WorldCanvas();
        jPanel1 = new javax.swing.JPanel();
        closeButton = new javax.swing.JButton();
        jPanel2 = new javax.swing.JPanel();
        startButton = new javax.swing.JButton();
        stopButton = new javax.swing.JButton();
        resetButton = new javax.swing.JButton();
        pauseButton = new javax.swing.JButton();
        resumeButton = new javax.swing.JButton();

        setDefaultCloseOperation(javax.swing.WindowConstants.DISPOSE_ON_CLOSE);

        worldCanvas.setPreferredSize(new java.awt.Dimension(2000, 2000));

        javax.swing.GroupLayout worldCanvasLayout = new javax.swing.GroupLayout(worldCanvas);
        worldCanvas.setLayout(worldCanvasLayout);
        worldCanvasLayout.setHorizontalGroup(
            worldCanvasLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 2000, Short.MAX_VALUE)
        );
        worldCanvasLayout.setVerticalGroup(
            worldCanvasLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGap(0, 2000, Short.MAX_VALUE)
        );

        jScrollPane1.setViewportView(worldCanvas);

        jPanel1.setBorder(javax.swing.BorderFactory.createEtchedBorder());

        java.util.ResourceBundle bundle = java.util.ResourceBundle.getBundle("bobbietalk/Labels"); // NOI18N
        closeButton.setText(bundle.getString("dialog.interpreter.buton.ok")); // NOI18N
        closeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                closeButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout jPanel1Layout = new javax.swing.GroupLayout(jPanel1);
        jPanel1.setLayout(jPanel1Layout);
        jPanel1Layout.setHorizontalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, jPanel1Layout.createSequentialGroup()
                .addContainerGap(853, Short.MAX_VALUE)
                .addComponent(closeButton)
                .addContainerGap())
        );
        jPanel1Layout.setVerticalGroup(
            jPanel1Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel1Layout.createSequentialGroup()
                .addContainerGap()
                .addComponent(closeButton)
                .addContainerGap(15, Short.MAX_VALUE))
        );

        jPanel2.setBorder(javax.swing.BorderFactory.createEtchedBorder());

        startButton.setText(bundle.getString("dialog.interpreter.buton.start")); // NOI18N
        startButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                startButtonActionPerformed(evt);
            }
        });

        stopButton.setText(bundle.getString("dialog.interpreter.buton.stop")); // NOI18N
        stopButton.setEnabled(false);
        stopButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                stopButtonActionPerformed(evt);
            }
        });

        resetButton.setText("Reset");
        resetButton.setEnabled(false);
        resetButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                resetButtonActionPerformed(evt);
            }
        });

        pauseButton.setText(bundle.getString("dialog.interpreter.buton.pause")); // NOI18N
        pauseButton.setEnabled(false);
        pauseButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                pauseButtonActionPerformed(evt);
            }
        });

        resumeButton.setText(bundle.getString("dialog.interpreter.buton.resume")); // NOI18N
        resumeButton.setEnabled(false);
        resumeButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                resumeButtonActionPerformed(evt);
            }
        });

        javax.swing.GroupLayout jPanel2Layout = new javax.swing.GroupLayout(jPanel2);
        jPanel2.setLayout(jPanel2Layout);
        jPanel2Layout.setHorizontalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel2Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addGroup(jPanel2Layout.createSequentialGroup()
                        .addComponent(pauseButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(resumeButton))
                    .addComponent(startButton))
                .addGap(22, 22, 22)
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(resetButton)
                    .addComponent(stopButton))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );
        jPanel2Layout.setVerticalGroup(
            jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(jPanel2Layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(startButton)
                    .addComponent(resetButton))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(jPanel2Layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(stopButton)
                    .addComponent(pauseButton)
                    .addComponent(resumeButton))
                .addContainerGap(24, Short.MAX_VALUE))
        );

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                    .addComponent(jPanel1, javax.swing.GroupLayout.Alignment.LEADING, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                    .addComponent(jPanel2, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                .addContainerGap())
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup()
                    .addContainerGap()
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 920, Short.MAX_VALUE)
                    .addContainerGap()))
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap(608, Short.MAX_VALUE)
                .addComponent(jPanel2, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(jPanel1, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addContainerGap())
            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                .addGroup(layout.createSequentialGroup()
                    .addContainerGap()
                    .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 582, Short.MAX_VALUE)
                    .addGap(196, 196, 196)))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void startButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_startButtonActionPerformed

        startController();
        resume();

        pauseButton.setEnabled(true);
        startButton.setEnabled(false);
        stopButton.setEnabled(true);

    }//GEN-LAST:event_startButtonActionPerformed

    private void closeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_closeButtonActionPerformed

        if (!hold.isEmpty()) {
            hold.remove();
        }

        userDialog.dispose();
        this.dispose();

    }//GEN-LAST:event_closeButtonActionPerformed

    private void stopButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_stopButtonActionPerformed

        stop();

    }//GEN-LAST:event_stopButtonActionPerformed

    private void resetButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_resetButtonActionPerformed
        // TODO add your handling code here:
    }//GEN-LAST:event_resetButtonActionPerformed

    private void pauseButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_pauseButtonActionPerformed

        hold.remove();
        pauseButton.setEnabled(false);
        resumeButton.setEnabled(true);


    }//GEN-LAST:event_pauseButtonActionPerformed

    private void resumeButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_resumeButtonActionPerformed

        hold.add(new Integer(1));
        pauseButton.setEnabled(true);
        resumeButton.setEnabled(false);

    }//GEN-LAST:event_resumeButtonActionPerformed

    public void setRobot(AbstractSimpleRobot robot) {
        this.robot = robot;



    }
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JButton closeButton;
    private javax.swing.JPanel jPanel1;
    private javax.swing.JPanel jPanel2;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JButton pauseButton;
    private javax.swing.JButton resetButton;
    private javax.swing.JButton resumeButton;
    private javax.swing.JButton startButton;
    private javax.swing.JButton stopButton;
    private bobbietalk.simulator.gui.WorldCanvas worldCanvas;
    // End of variables declaration//GEN-END:variables

    public void runCode(String code) {

        final NxtRobot robot = new NxtRobot();

        robot.setDt(1.0 / 25);
        this.code = code;

        setRobot(robot);
        worldCanvas.setRobot(robot);

    }

    public String getCode() {
        return code;
    }

    public AbstractSimpleRobot getRobot() {
        return robot;
    }

    public void startController() {

        final BshController fwCnt = new BshController();
        fwCnt.setProgram(code.toString());
        fwCnt.setRobot(getRobot());
        fwCnt.addObserver(new Observer() {

            @Override
            public void update(Observable o, Object arg) {
                if (fwCnt.getState() == BshController.State.ERRORED) {
                    
                    userDialog.setTitle("Error in simulator");
                    userDialog.setSummaryLabel("Simulator reported the following error");
                    userDialog.setDetailsLabel(fwCnt.getError().toString());
                    
                    userDialog.setVisible(true);
                    
//                    System.err.println("error in executing the controller");
//                    fwCnt.getError().printStackTrace();
                    
                }
            }
        });
        

        controllerThread = new Thread(fwCnt);
        controllerThread.start();



    }

    public void stop() {

        controllerThread.interrupt();

        startButton.setEnabled(true);
        stopButton.setEnabled(false);
        pauseButton.setEnabled(false);
        resumeButton.setEnabled(false);
        
        suspend();
    }

    private void initSimulation() {

        simulationThread = new Thread(new Runnable() {
            @Override
            public void run() {

                // int secs = 30;
                // int frames = 60 * secs;


                long frames = 0;

                while (true) {

                    if (frames % 25 == 0) {
                        try {
//                            System.out.println(new Date() + ": suspending ...");
                            checkRunning();
//                            System.out.println(new Date() + ": awoken");
                        } catch (InterruptedException ex) {
                            Utilities.getLogger().log(Level.SEVERE, null, ex);
                        }

                    }

                    frames++;

                    if (robot != null) {
                        final SimpleMotorsPlatform motors = robot.getMotors();
                        motors.updateSpeed();
                        motors.updatePosition();
                        motors.markChanged();
                        motors.notifyObservers();
                    }

                    try {

                        Thread.sleep(1000 / 25);

                    } catch (InterruptedException ex) {
                        Utilities.getLogger().log(Level.SEVERE, null, ex);
                        break;
                    }

                }


            }

            private void checkRunning() throws InterruptedException {
                Integer i = hold.take();
                hold.put(i);
            }
        });

        simulationThread.start();
    }

    public void suspend() {

        if (!hold.isEmpty()) {
            hold.remove();
        }

    }

    @Override
    public void setVisible(boolean v) {

        resetButtonState();

        resume();

        super.setVisible(v);
    }

    private void resume() {

        if (hold.isEmpty()) {
            hold.add(new Integer(1));
        }

    }

    private void resetButtonState() {
        
        pauseButton.setEnabled(false);
        resumeButton.setEnabled(false);
        startButton.setEnabled(true);
        stopButton.setEnabled(false);
    }
}
